package ongd.negocio.gestion;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import ongd.negocio.sesion.ControladorSesion;
import ongd.servicios.persistencia.ControladorPersistencia;
import ongd.servicios.persistencia.exceptions.IllegalOrphanException;
import ongd.servicios.persistencia.exceptions.NonexistentEntityException;
import ongd.servicios.persistencia.exceptions.PreexistingEntityException;

/**
 * Clase abstracta que realizará ciertas operaciones comunes a los socios
 * y padrinos de la base de datos.
 *
 * @author <a href="mailto:egi0002@alu.ubu.es">Elena García Iglesias</a>
 * @author <a href="mailto:cms0021@alu.ubu.es">Cristina Martínez Sanz</a>
 * @version 1.0 20120124
 */
public abstract class ControladorPersona {

    /**
     * Objeto del tipo controlador persistencia que nos servirá para crear 
     * las instancias de las diferentes entidades.
     */
    private ControladorPersistencia contP;
    /**
     * Objeto del tipo controlador sesion que nos servirá para sabre 
     * en que sesion estamos.
     */
    private ControladorSesion contSesion;

    /**
     * Constructor de la clase.
     * @param contP controlador Persistencia
     * @param contSesion sesión en la que estamos
     */
    public ControladorPersona(ControladorPersistencia contP, ControladorSesion contSesion) {
        this.contP = contP;
        this.contSesion = contSesion;
    }

    /**
     * Método que se encargará de borrar un persona de la base de datos.
     * @param persona Objeto de tipo persona que será el que se borrará de la
     * base de datos
     * @return true o false en función de si la operación se ha realizado
     * correctamente
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public abstract boolean borrarPersona(Persona persona) throws IllegalOrphanException, NonexistentEntityException, Exception;

    /**
     * Método que da de baja a una persona.
     * @param persona Objeto de tipo persona que será el dado de baja
     * @return true si la operación se realiza correctamente
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public abstract boolean darBajaPersona(Persona persona) throws IllegalOrphanException, NonexistentEntityException, Exception;   

    /**
     * Método que busca una persona sabiendo el dni.
     * @param dniPersona Dni persona
     * @return persona
     */
    public Persona buscarPersona(String dniPersona) {
        return (Persona) contP.obtenerEntidad(Persona.class, dniPersona);
    }

    /**
     * Método que crear el pago fraccionado a partir de una persona y una lista
     * de fraccionados.
     * @param persona persona a la que asociaremos las fracciones
     * @param listaF lista de fracciones que vamos a crear
     * @throws PreexistingEntityException
     * @throws Exception 
     */
    public void crearFraccionados(Persona persona, List<Fraccionado> listaF) throws PreexistingEntityException, Exception {
        if (!persona.esVoluntario()) {
            int idFraccionado = 0;
            for (Fraccionado f : listaF) {
                f.setFraccionadoPK(new FraccionadoPK(idFraccionado, persona.getDni()));
                f.setPersona(persona);
                contP.crearEntidad(f);
                idFraccionado++;
            }
        }
    }

    /**
     * Método que crea las cuotas asociadas a una persona.
     * @param persona Persona a la que se asociarán las cuotas creadas
     * @param cantidad Cantidad total que deben de sumar las cuotas
     * @throws PreexistingEntityException
     * @throws Exception 
     */
    public void crearCuotas(Persona persona, double cantidad) throws PreexistingEntityException, Exception {
        //Si la lista de fracciones no esta vacia
        if (!persona.getFraccionadoList().isEmpty()) {
            List<Fraccionado> listaF = persona.getFraccionadoList();
            List<Cuota> listaC = obtenerCuotasNecesarias(listaF);
            for (Cuota cuota : listaC) {
                cuota.setCantidad(cantidad / listaC.size());
                contP.crearEntidad(cuota);
            }
        }
    }

    /**
     * Método que devuelve una lista con las cuotas que deberemos crear
     * en un función de la fecha actual y de si ya están creadas o no.
     * @param listaF tipo de pago elegido
     * @return lista de cuotas
     */
    public List<Cuota> obtenerCuotasNecesarias(List<Fraccionado> listaF) {
        List<Cuota> listaC = new ArrayList<Cuota>();
        if (!listaF.isEmpty()) {
            Calendar calendarioActual = Calendar.getInstance();
            //Obtengo el mes en el que estamos, de 0 a 11.
            int mesActual = calendarioActual.get(Calendar.MONTH);
            List<Cuota> lista = listaF.get(0).getPersona().getCuotaList();
            int idCuota = 0;
            //Si a lista de cuotas de la persona esta vacia inicializo el idCuota a 0 sino al siguiente que hay creado
            if (!lista.isEmpty()) {
                idCuota = lista.get(lista.size() - 1).getCuotaPK().getIdCuota() + 1;
            }
            boolean repetida = false;
            for (Fraccionado f : listaF) {
                //Obtengo el mes de la fraccion.
                int mesFraccionado = f.obtenerMes();
                //Comparo con el mes actual con el mes de la fraccion
                if (mesActual <= mesFraccionado) {
                    //Si el mes actual es menor que el de la fraccion añadimos la cuota a la lista
                    Calendar cFechaPago = Calendar.getInstance();
                    cFechaPago.set(Calendar.MONTH, mesFraccionado);
                    cFechaPago.set(Calendar.DAY_OF_MONTH, 2);
                    cFechaPago.set(Calendar.HOUR_OF_DAY, 9);
                    for (Cuota c : f.getPersona().getCuotaList()) {
                        //comprobaré que no existe una cuota con la misma fecha ya creada.
                        if (c.getFechaPago().compareTo(cFechaPago.getTime()) == 0) {
                            repetida = true;
                        }
                    }
                    if (!repetida) {
                        //Si no hay una cuota ya creada con esa misma fecha la añadimos a la lista
                        Cuota cuota = new Cuota(idCuota, f.getPersona().getDni());
                        cuota.setPersona(f.getPersona());
                        cuota.setFechaCreacion(new Date());
                        cuota.setEstado("P");
                        cuota.setFechaPago(cFechaPago.getTime());
                        listaC.add(cuota);
                        idCuota++;
                    }
                }
            }
            /*Si la lista de cuotas está vacia implica que estamos al último mes
             * del año.*/
            if (listaC.isEmpty()) {
                Calendar cFechaPago = Calendar.getInstance();
                cFechaPago.set(Calendar.MONTH, 11);
                cFechaPago.set(Calendar.DAY_OF_MONTH, 2);
                cFechaPago.set(Calendar.HOUR_OF_DAY, 9);
                for (Cuota c : listaF.get(0).getPersona().getCuotaList()) {
                    //comprobaré que no existe una cuota con la misma fecha ya creada.
                    if (c.getFechaPago().compareTo(cFechaPago.getTime()) == 0) {
                        repetida = true;
                    }
                }
                if (!repetida) {
                    Cuota cuota = new Cuota(idCuota, listaF.get(0).getPersona().getDni());
                    cuota.setPersona(listaF.get(0).getPersona());
                    cuota.setFechaCreacion(new Date());
                    cuota.setEstado("P");
                    cuota.setFechaPago(cFechaPago.getTime());
                    listaC.add(cuota);
                }
            }
        }
        return listaC;
    }

    /**
     * Método que obtiene la lista de cuotas existentes en la base de datos
     * dependiendo de la cadena que se pase por parámetro devolverá las cuotas
     * referentes a los socios o las referentes a los padrinos que estén dados 
     * de alta.
     * @param tipo String que determinará que cuotas devolver
     * @return listaC
     */
    public List<Cuota> obtenerListaCuotas(String tipo) {
        List<? extends Entidad> listaCuotas = getContP().obtenerEntidades(Cuota.class);
        List<Cuota> listaC = new ArrayList<Cuota>();
        for (Entidad c : listaCuotas) {
            Persona p = ((Cuota) c).getPersona();
            if (p.getTipo().compareTo(tipo) == 0 && p.getFechaBaja() == null) {
                listaC.add((Cuota) c);
            }
        }
        Collections.sort(listaC, new Comparator<Cuota>() {

            @Override
            public int compare(Cuota o1, Cuota o2) {
                return o1.getFechaCreacion().compareTo(o2.getFechaCreacion());
            }
        });
        return listaC;
    }

    /**
     * Método que obtiene una lista de cuotas que corresponden con las cuotas
     * de una persona en un año.
     * @param persona Socio
     * @param anno Año de las cuotas
     * @return cuotasAnuales
     */
    public List<Cuota> obtenerCuotasAnuales(Persona persona, int anno) {
        List<Cuota> cuotasAnuales = new ArrayList<Cuota>();
        //Establezco el 1 de enero de el año pasado
        Calendar inicioAnno = Calendar.getInstance();
        inicioAnno.set(Calendar.DAY_OF_MONTH, 1);
        inicioAnno.set(Calendar.MONTH, 0);
        inicioAnno.set(Calendar.YEAR, anno);
        //Establezco el 31 de diciembre de el año pasado
        Calendar finAnno = Calendar.getInstance();
        finAnno.set(Calendar.DAY_OF_MONTH, 31);
        finAnno.set(Calendar.MONTH, 11);
        finAnno.set(Calendar.YEAR, anno);
        //Obtengo la lista de cuotas de la persona.
        List<Cuota> cuotas = persona.getCuotaList();
        //Recorró la lista en busca de las cuotas que estén dentro de ambas fechas
        for (Cuota c : cuotas) {
            if (inicioAnno.getTime().compareTo(c.getFechaPago()) <= 0 && c.getFechaPago().compareTo(finAnno.getTime()) <= 0) {
                cuotasAnuales.add(c);
            }
        }
        return cuotasAnuales;
    }

    /**
     * Método que devuelve una lista con las cuotas que tiene canceladas
     * esa persona a partir de la fecha actual.
     * @param listaC Lista de cuotas
     * @return lista de cuotas
     */
    public List<Cuota> obtenerCuotasCanceladas(List<Cuota> listaC) {
        //Establezco la fecha actual
        Calendar fechaActual = Calendar.getInstance();
        List<Cuota> cuotasCanceladas = new ArrayList<Cuota>();
        for (Cuota c : listaC) {
            if (c.esCancelada()) {
                if (fechaActual.getTime().compareTo(c.getFechaPago()) <= 0) {
                    cuotasCanceladas.add(c);
                }
            }
        }
        return cuotasCanceladas;
    }

    /**
     * Método que edita un pago fraccionado.
     * @param listaF lista con los fraccionados a modificar
     * @throws Exception
     */
    public void editarPagoFraccionado(List<Fraccionado> listaF) throws Exception {
        for (Fraccionado f : listaF) {
            contP.editarEntidad(f);
        }
    }

    /**
     * Método que edita las cuotas referentes a una persona.
     * @param persona Objeto del tipo persona asociado a las cuotas a editar
     * @throws Exception 
     */
    public void editarCuota(Persona persona) throws Exception {
        double cantidadAbonadas = 0;
        double cantidadPendienteAntes = 0;
        List<Cuota> listaPendientes = new ArrayList<Cuota>();
        /*Recorreré la lista de cuotas para ir calculando la cantidad que hay 
         * abonada, la que queda pendiente de abonar y guardaré las cuotas pendientes
         * en una lista para más adelante irlas editando. */
        for (Cuota c : persona.getCuotaList()) {
            if (c.esAbonada()) {
                //Si la cuota está abonada sumaré la cantidad para saber la cantidad que ha sido abonada
                cantidadAbonadas += c.getCantidad();
            }
            if (c.esPendiente()) {
                //si la cuota está pendiente añadiré esta cuota a la lista
                listaPendientes.add(c);
                cantidadPendienteAntes += c.getCantidad();
            }
        }
        //Calculo la cantidad pendiente de abonar
        double cantidadPendiente = persona.getCantidad() - cantidadAbonadas;
        /* Si la cantidad pendiente es igual a la obtenida antes, significa 
         * que no se ha modificado la cantidad de la persona y por tanto solo 
         * editaremos las cuotas que tengamos pendientes*/
        if (cantidadPendienteAntes == cantidadPendiente) {
            for (Cuota c : listaPendientes) {
                contP.editarEntidad(c);
            }
        } else {
            double cantidadCuota = 0;
            if (cantidadPendiente <= 0) {
                cantidadCuota = 0;
            } else {
                cantidadCuota = cantidadPendiente / listaPendientes.size();
            }
            for (Cuota c : listaPendientes) {
                c.setCantidad(cantidadCuota);
                contP.editarEntidad(c);
            }
        }
    }

    /**
     * Método que edita el estado de una cuota en función de una cadena pasada
     * por parámetro.
     * @param cuota Cuota a la que editaremos el estado
     * @param estado Estado nuevo
     * @return true o false en función de si la cuota ha sido editada o no
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public boolean editarEstadoCuota(Cuota cuota, String estado) throws IllegalOrphanException, NonexistentEntityException, Exception {
        boolean modificada = false;
        if (cuota.getPersona().getFechaBaja() == null) {
            //Si la persona no tiene fecha de baja podré modificar la cuota
            if (estado.compareTo("P") == 0 && !cuota.esPendiente()) {
                cuota.establecerPendiente();
                contP.editarEntidad(cuota);
                modificada = true;
            } else if (estado.compareTo("A") == 0 && !cuota.esAbonada()) {
                cuota.establecerAbonada();
                contP.editarEntidad(cuota);
                modificada = true;
            } else if (estado.compareTo("C") == 0 && !cuota.esCancelada()) {
                cuota.establecerCancelada();
                contP.editarEntidad(cuota);
                modificada = true;
            }
        } else {
            modificada = false;
        }
        return modificada;
    }

    /**
     * Método que cancela todas las cuotas que estén a estado Pendiente de una
     * lista de cuotas pasada por parámetro.
     * @param listaC lista de cuotas
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public void cancelarCuotasPendientes(List<Cuota> listaC) throws IllegalOrphanException, NonexistentEntityException, Exception {
        //Cancelaré las cuotas que el socio tenga pendientes
        for (Cuota c : listaC) {
            if (c.esPendiente()) {
                c.setEstado("C");
                contP.editarEntidad(c);
            }
        }
    }

    /**
     * Método establece una lista de cuotas a pendientes.
     * @param listaC lista de cuotas
     * @param cantidad Cantidad que debe de sumar todas las cuotas de la lista
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public void establecerCuotasPendientes(List<Cuota> listaC, double cantidad) throws IllegalOrphanException, NonexistentEntityException, Exception{
        double cantidadCuota = cantidad / listaC.size();
        for(Cuota c : listaC){
            if(!c.esPendiente()){
                c.establecerPendiente();
                c.setCantidad(cantidadCuota);
                contP.editarEntidad(c);
            }
        }
    }
    
    /**
     * Metodo que borrar una lista de pago fraccionado.
     * @param listaF lista que contiene los pagos fraccionados a borrar
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException 
     */
    public void borrarPagoFraccionado(List<Fraccionado> listaF) throws IllegalOrphanException, NonexistentEntityException {
        for (int i = listaF.size() - 1; i >= 0; i--) {
            contP.destroyEntidad(Fraccionado.class, listaF.get(i).getFraccionadoPK());
        }
    }

    /**
     * Método que borra las cuotas de un socio.
     * @param listaC lista de cuotas a borrar
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException 
     */
    public void borrarCuotas(List<Cuota> listaC) throws IllegalOrphanException, NonexistentEntityException {
        for (int i = listaC.size() - 1; i >= 0; i--) {
            contP.destroyEntidad(Cuota.class, listaC.get(i).getCuotaPK());
        }
    }

    /**
     * Método que borra las cuotas que están pendientes de una lista de cuotas
     * pasada por parámetro.
     * @param listaC lista de cuotas
     * @return suma de la cantidad abonada
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException 
     */
    public double borrarCuotasPendientes(List<Cuota> listaC) throws IllegalOrphanException, NonexistentEntityException {
        double cantidad = 0;
        for (int i = listaC.size() - 1; i >= 0; i--) {
            Cuota c = listaC.get(i);
            if (c.esPendiente()) {
                contP.destroyEntidad(Cuota.class, c.getCuotaPK());
            }
            if (c.esAbonada()) {
                cantidad += c.getCantidad();
            }
        }
        return cantidad;
    }

    /**
     * Método que devuelve una lista con las personas que estan dadas de alta
     * en función del tipo pasado por parámetro.
     * @param tipo Tipo persona
     * @return listaP
     */
    public List<Persona> obtenerListaPersonaAlta(String tipo) {
        List<? extends Entidad> listaPersonas = getContP().obtenerEntidades(Persona.class);
        List<Persona> listaP = new ArrayList<Persona>();
        for (Entidad p : listaPersonas) {
            if (((Persona) p).getTipo().compareTo(tipo) == 0 && ((Persona) p).getFechaBaja() == null) {
                listaP.add((Persona) p);
            }
        }
        return listaP;
    }

    /**
     * Método uqe devuelve una lista con las personas que están dadas de baja
     * en función del tipo pasado por parámetro.
     * @param tipo Tipo persona
     * @return listaP
     */
    public List<Persona> obtenerListaPersonaBaja(String tipo) {
        List<? extends Entidad> listaPersonas = getContP().obtenerEntidades(Persona.class);
        List<Persona> listaP = new ArrayList<Persona>();
        for (Entidad p : listaPersonas) {
            if (((Persona) p).getTipo().compareTo(tipo) == 0 && ((Persona) p).getFechaBaja() != null) {
                listaP.add((Persona) p);
            }
        }
        return listaP;
    }
    
    /**
     * Método que genera las cuotas automáticas del nuevo año.
     * @throws PreexistingEntityException
     * @throws Exception 
     */
    public void generarCuotasAutomaticas() throws PreexistingEntityException, Exception{
        Calendar calendarioActual = Calendar.getInstance();
        //Obtengo el año actual.
        int annoActual = calendarioActual.get(Calendar.YEAR);
        //Sacamos la primera fila de la tabla configuración y nos quedamos con el año de la ultima fecha de creación de las cuotas.
        List<? extends Entidad> configuraciones=  contP.obtenerEntidades(Configuracion.class);
        if(!configuraciones.isEmpty()){
            Configuracion config= (Configuracion)configuraciones.get(0);
            int annoConf =Integer.parseInt(new SimpleDateFormat("yyyy").format(config.getFechaCuotas()));
            if(annoActual>annoConf){
                List<Persona> personas=new ArrayList<Persona>();
                personas.addAll(obtenerListaPersonaAlta("S"));
                personas.addAll(obtenerListaPersonaAlta("P"));
                contP.iniciarTransaccion();
                for(Persona p: personas){
                    //Miramos si la persona tiene ya creada alguna cuota de ese año
                    if(!p.getCuotaList().isEmpty()){
                        if(Integer.parseInt(new SimpleDateFormat("yyyy").format(p.getCuotaList().get(0).getFechaCreacion()))<annoActual){
                            crearCuotas(p, p.getCantidad());
                        }
                    }
                }
                //Recorremos la lista de configuracion y modificamos las fechas de cuotas.
                for(Entidad conf: configuraciones){
                    ((Configuracion)conf).setFechaCuotas(new Date());
                    contP.editarEntidad(conf);
                }
                contP.terminarTransaccion();
            }
        }
    }    
    
    /**
     * Metodo que devuelve la lista de de cuotas pendientes sin alertas entre dos fechas.
     * @param min fecha minima a partir de la cual obtenermos las cuotas.
     * @param max fecha maxima hasta la cual obtenemos las cuotas.
     * @return lista de cuotas.
     */
    public List<Cuota> obtenerCuotasSinAlertas(Date min, Date max){
        List<Cuota> cuotas= new ArrayList<Cuota>();
        for(Cuota cuota: obtenerCuotas()){
            if(cuota.getFechaPago().compareTo(min)>=0 && cuota.getFechaPago().compareTo(max)<0 
                    && cuota.esPendiente() && !cuota.getAlerta()){
                cuotas.add((Cuota)cuota);
            }
        }
        return cuotas;
    }
    
    /**
     * Método que devuelve las cuotas pendientes anteriores a una fecha.
     * @param min fecha hasta la que se buscan las cuotas pendientes.
     * @return lista de cuotas.
     */
    public List<Cuota> obtenerCuotasPendientesAvisadas(Date min){
        List<Cuota> cuotas= new ArrayList<Cuota>();
        for(Cuota cuota:obtenerCuotas()){
            if(cuota.getFechaPago().compareTo(min)<0 && cuota.esPendiente()){
                cuotas.add(cuota);
            }
        }
        return cuotas;
    }
    
    /**
     * Método que devuelve una lista con todas las cuotas de la base de datos.
     * @return lista de cuotas
     */
    public List<Cuota> obtenerCuotas(){
        return (List<Cuota>) contP.obtenerEntidades(Cuota.class);
    }
    
    /**
     * Método que pone la variable de alertas a true de una lista de cuotas.
     * @param cuotas lista de cuotas que vamosa  modificar.
     * @throws IllegalOrphanException
     * @throws NonexistentEntityException
     * @throws Exception 
     */
    public void establecerCuotasConAlertas(List<Cuota> cuotas) throws IllegalOrphanException, NonexistentEntityException, Exception{
        for(Cuota c: cuotas){
            c.setAlerta(true);
            contP.editarEntidad(c);
        }
    }

    /**
     * Método que devuelve un objeto del tipo ControladorPersistencia.
     * @return contP
     * @see #setContP
     */
    public ControladorPersistencia getContP() {
        return contP;
    }

    /**
     * Método que establece un objeto del tipo ControladorPersistencia.
     * @param contP Nuevo valor de la variable
     * @see #getContP
     */
    public void setContP(ControladorPersistencia contP) {
        this.contP = contP;
    }

    /**
     * Método que devuelve un objeto del tipo ControladorSesion.
     * @return contSesion
     * @see #setContSesion
     */
    public ControladorSesion getContSesion() {
        return contSesion;
    }

    /**
     * Método que establece un objeto del tipo ControladorSesion.
     * @param contSesion Nuevo valor de la variable
     * @see #getContSesion
     */
    public void setContSesion(ControladorSesion contSesion) {
        this.contSesion = contSesion;
    }
}
